package mips;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import javax.swing.JFrame;

import org.apache.log4j.Logger;

import ui.VramDisplay;

import compiler.BasicBlock;

import mips.instructions.Instruction;

public class Main {

	private static final Logger logger = Logger.getLogger(mips.Main.class);

	public static final boolean interpret = true;
	
	public static final boolean tracing = false;
	
	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {

		JFrame frame = new JFrame("VRAM Display");
		VramDisplay vDisp = new VramDisplay();
		frame.add(vDisp);
		frame.setVisible(true);
		frame.setSize(320, 240 + 22);
		
		int fileSize = 8*1048576;
		
		n64.MemoryManager.ROM = new int[fileSize/4];
		
		Random r = new Random();
		
		for(int i=0;i<fileSize;i+=4) {
		
			int nextWord = r.nextInt();
			n64.MemoryManager.ROM[i/4] = nextWord;
		} 
		
		int vram[];
		
/*		File marioFile = new File("c:\\FIRE.BIN");
		//File marioFile = new File("mario64.v64");
		
		FileInputStream fis = new FileInputStream(marioFile);
		
		int fileSize = (int) marioFile.length();

		int bytesRead = 0;
		byte[] marioRom = new byte[fileSize];
		while((bytesRead = fis.read(marioRom)) != -1) ;

		n64.MemoryManager.ROM = new int[fileSize/4];
		
		if((((int)marioRom[0]) & 0xff) == 0x80 && (((int)marioRom[1]) & 0xff) == 0x37) {
			//System.out.println("x80 x37");
			for(int i=0;i<marioRom.length;i+=4) {
				
				byte tmp = marioRom[i];
				marioRom[i] = marioRom[i+3];
				marioRom[i+3] = tmp;
				
				tmp = marioRom[i+1];
				marioRom[i+1] = marioRom[i+2];
				marioRom[i+2] = tmp;
			}
		}
		else if((((int)marioRom[0]) & 0xff) == 0x37 && (((int)marioRom[1]) & 0xff) == 0x80) {
			//System.out.println("x37 x80");
			for(int i=0;i<marioRom.length;i+=4) {
				
				byte tmp = marioRom[i];
				marioRom[i] = marioRom[i+2];
				marioRom[i+2] = tmp;
				
				tmp = marioRom[i+1];
				marioRom[i+1] = marioRom[i+3];
				marioRom[i+3] = tmp;
			}
		}

		for(int i=0;i<fileSize;i+=4) {
		
			//int nextWord = ((marioRom[i+0] << 24) & 0xFF000000) | ((marioRom[i+1] << 16) & 0x00FF0000) | ((marioRom[i+2] << 8) & 0x0000FF00) | ((marioRom[i+3]) & 0x000000FF);
			int nextWord = ((marioRom[i+0] << 0) & 0x000000FF) | ((marioRom[i+1] << 8) & 0x0000FF00) | ((marioRom[i+2] << 16) & 0x00FF0000) | ((marioRom[i+3] << 24) & 0xFF000000);
			//logger.info(String.format("x%08X", nextWord));
			
			n64.MemoryManager.ROM[i/4] = nextWord;
			//n64.MemoryManager.ROM[i/4] = marioRom[i/4];
		}

		//0x0020 - 0x0033
		for(int i=0x20;i<0x33;i++) {
			System.out.print((char)marioRom[i]);
		}
*/		
		mips.R4300i.init();
		mips.Fire.writeFireToMemory();
		////mips.Fibonacci.writeFibToMemory();

		/*int fibn = 25;
		n64.MemoryManager.RDRAM[4] = 0x24040000 + fibn; // last byte is n for fib(n), 0x1B == 27*/
			
		//recompiled.InitCpuTargets.initCpuTargets();
		
		long instructionsExecuted = 0;
	
		long start = System.currentTimeMillis();
			
		while (true) {
		
			//logger.info("Executing :" + String.format("0x%08X \n",mips.CPU.PC));
			
			mips.R4300i.GPR[0] = 0;
					
			try {
				
				if(tracing == false && interpret == false) {
					
					if(mips.R4300i.targets.get(mips.R4300i.PC) != null) {
						
						compiler.BasicBlock nextBlock = mips.R4300i.targets.get(mips.R4300i.PC);
						
						//logger.info("Executing block: " + nextBlock.toString());
						
						nextBlock.execute();
						instructionsExecuted += nextBlock.blockLength()-1;
						
						/*if( (System.currentTimeMillis() - start) % 33 == 0 ) {
							vDisp.update();
							vDisp.repaint();
						}*/

					}
					else {
						
						int instruction = n64.MemoryManager.readWord(mips.R4300i.PC);

						Instruction.dispatch(instruction);
						instructionsExecuted +=1;
						
						/*if( (System.currentTimeMillis() - start) % 33 == 0 ) {
							vDisp.update();
							vDisp.repaint();
						}*/

					}
				}
				else {
					
					int instruction = n64.MemoryManager.readWord(mips.R4300i.PC);
/*					logger.info("//"+String.format("0x%08X",mips.R4300i.PC)+"\n");
					logger.info("//"+Instruction.getOpcode((instruction >> 26) &  0x0000003F).getName(instruction)+"\n"); 
					logger.info(Instruction.getOpcode((instruction >> 26) &  0x0000003F).emit(instruction)); */
					
/*					if(mips.R4300i.PC == 0xA0700100) {
						
						for(int i=0;i<31;i++) {
						
						logger.info("//"+String.format("0x%08X",mips.R4300i.GPR[i]));
						}
					}
	*/				
					Instruction.dispatch(instruction);
					instructionsExecuted +=1;
					

					if( ((System.currentTimeMillis() - start) % 10000) == 0 ) {
						logger.info(String.format("0x%08X", n64.VI.REG[n64.VI.VI_ORIGIN]));
						//logger.info(String.format("0x%08X", n64.MemoryManager.readWord(n64.VI.REG[n64.VI.VI_ORIGIN])));
						vDisp.update();
						vDisp.repaint();
					}

					/*if((instructionsExecuted % 0xFFFFFFFF) == 0) {
						logger.info("//"+String.format("0x%08X",mips.R4300i.PC)+"\n");
					}*/
					
				}
			}
			/**
			 * from R4000 Microprocessor Users Manual 
			 * 
			 * When an exception occurs, the CPU loads the Exception Program Counter
			 * (EPC) register with a location where execution can restart after the
			 * exception has been serviced. The restart location in the EPC register is the
			 * address of the instruction that caused the exception or, if the instruction
			 * was executing in a branch delay slot, the address of the branch instruction
			 * immediately preceding the delay slot.
			 */
			catch(mips.exceptions.BreakpointException breakpointException) {
				
				logger.info("caught MIPS Breakpoint exception");
				
				/**
				 * Execution of the current instruction caused breakpoint exception
				 * to be generated. Handle and return to normal execution stream.
				 */
				if(mips.R4300i.DELAY_SLOT_EXEC) {
					mips.COP0.CONTROL[14] = mips.R4300i.PC - 4;
				}
				else {
					mips.COP0.CONTROL[14] = mips.R4300i.PC;
				}
			}
			catch(mips.exceptions.OverflowException overflowException) {

				logger.info("caught MIPS Overflow exception @ PC: " + String.format("0x%08X", overflowException.getPC()));
				
				/**
				 * Execution of the current instruction caused an overflow exception
				 * to be generated. Handle and return to normal execution stream.
				 */
				if(mips.R4300i.DELAY_SLOT_EXEC) {
					mips.COP0.CONTROL[14] = mips.R4300i.PC - 4;
				}
				else {
					mips.COP0.CONTROL[14] = mips.R4300i.PC;
				}
			}
			catch(mips.exceptions.TrapException trapException) {
				
				logger.info("caught MIPS Trap exception");
				
				/**
				 * Execution of the current instruction caused a trap exception
				 * to be generated. Handle and return to normal execution stream.
				 */
				if(mips.R4300i.DELAY_SLOT_EXEC) {
					mips.COP0.CONTROL[14] = mips.R4300i.PC - 4;
				}
				else {
					mips.COP0.CONTROL[14] = mips.R4300i.PC;
				}
			}
			catch(mips.exceptions.DoublewordInstructionException dwException) {
				
				logger.error("caught Doubleword Instruction exception @ PC " + String.format("0x%08X", dwException.getPC()));
				break;
			}	
			catch(mips.exceptions.UnimplementedInstructionException dwException) {
				
				logger.error("caught Unimplemented Instruction exception @ PC " + String.format("0x%08X", dwException.getPC()));
				break;
			}
			catch(Exception e) {
				
				logger.error("caught unknown exception", e);
				break;
			}

			/*if(mips.R4300i.PC == 0xa0000028) { break; } 
			else if(mips.R4300i.PC == 0x80000028) { break; } 
			else if(mips.R4300i.PC == 0x00000028) { break; }*/ 
		}
		
		long end = System.currentTimeMillis();

		logger.info("Instructions executed: " + instructionsExecuted + " in " + ((end - start)/1000.0) + " seconds.");
		logger.info("Emulated clock speed: " + ((instructionsExecuted/((end - start)/1000.0))/1048576.0) + " MHz");
		
		logger.info(mips.R4300i.GPR[2]);
		
		List<Integer> sortedTargets = new ArrayList<Integer>(mips.R4300i.targets.keySet());
		Collections.sort(sortedTargets);

		if(tracing) {
			
			try {
				
				compiler.StaticRecompiler.emitBasicBlocks(sortedTargets);
			}
			catch(Exception e) {
				
				logger.error(e,e);
			}
		}
	}
	

}